import types
from django.db import models
from django.db.models import Q
from django.db.models.base import ModelBase
from DUMPS.middleware import get_current_user
from DUMPS.filters import ROW_FILTERS
# Create your models here.

VIEW_ACT = 'View'
CREATE_ACT = 'Create'
UPDATE_ACT = 'Update'
DELETE_ACT = 'Delete'

#some config constants
DEFAULT_PRECEDENCE = 10
MAX_FIELD_LEN = 255
RULE_CHAR = '*'
PK_SEP = ','
ACTOR_TYPES_CHOICES = (
        ('user', 'User'),
        ('group', 'Group'),
)
RULES_CHOICES = ( 
        ('deny', 'Deny'),
        ('allow', 'Allow'),
)
DENY = RULES_CHOICES[0][0]
ALLOW = RULES_CHOICES[1][0]

def print_db_queries ():
        from django import db
        print db.connection.queries


def get_user():
        user = get_current_user()
#	if not user:
#		print """User not set - either DUMPS middleware is not imported, or there is no web request to trigger it.
#			   Use DUMPS.middleware.set_current_user() to specify the user we are running as"""
        return user

class DUMPSModelBase (ModelBase):
        """Metaclass for PermissionedModels"""
        def __new__ (cls, name, bases, attrs):
                """ Sets DUMPS_ID to match the model's name, if it's not set in the model"""
                model = super (DUMPSModelBase,cls).__new__(cls, name, bases, attrs)
                if not (model._meta.abstract or hasattr(model,'DUMPS_ID')):
                        setattr(model,'DUMPS_ID',model._meta.object_name)
                if not model._meta.abstract  and hasattr(model._meta,'admin') and hasattr(model._meta.admin,'manager'):
                        # Fucking stupid fix for oldforms Admin; should be removed when NFA lands
                        model._meta.admin.manager = model._default_manager
                return model


class ModelManager(models.Manager):
        def get_query_set(self):
                permissions = Permission.objects.get_grouped_permission_rules \
                            (self.model.DUMPS_ID, VIEW_ACT,get_user())
                print self.model.DUMPS_ID
                if not permissions:
                        # if no permissions are defined, assume access is denied
                        return super(ModelManager, self).get_empty_query_set()
                base_qry = super(ModelManager, self).get_query_set()
                base_Q = Q()
                ignore_deny = True
                deny_buf = []
                allow_buf = []
                for level in sorted(permissions.keys()):
                        # Lowest Level deny permissions are ignored, as access is denied by default
                        # Permissions are processed from lower to higher precedence;
                        # Dict sorting here should be OK, as there should be just a few permission levels
                        if len(permissions[level]) > 1:
                                # we have composite permissions - multiple rules with the same precedence
                                Q_obj, rule_type = self.Q_from_samelevel_permissions (permissions[level])
                        else:
                                # we have a single permission for this level
                                rule = permissions[level][0]
                                rule_type = rule.rule
                                Q_obj = self.Q_from_permission (rule)
                        if rule_type not in (ALLOW, DENY):
                                # something is not right - the type of the permission is different than ALLOW/DENY; bail out
                                raise ValueError ('View permissions rules must match "%s" or "%s" - %s not understood' % (ALLOW, DENY,rule_type))
                        if ignore_deny:
                                if rule_type == DENY:
                                # we skip low-level deny rules cause we deny access by default
                                        continue
                                else:
                                # we got our first positive rule; we can no longer ignore deny rules
                                        ignore_deny = False
                        if rule_type == DENY:
                                # we just add the Q_obj to the negative list
                                deny_buf.append(Q_obj)
                                continue
                        # we got a postivie rule; what we do depends on the history recorded in allow_buf and deny_buf
                        if not deny_buf:
                                allow_buf.append(Q_obj)
                                continue
                        # so, we have a positive rule and a history with at least one deny rule; 
                        # lets make a Q object out of them and pass it as a filter

                        #base_qry = base_qry.filter (self.Q_from_Q_history  (allow_buf, deny_buf))
                        base_Q = base_Q | self.Q_from_Q_history  (allow_buf, deny_buf)
                        deny_buf = []
                        allow_buf = [Q_obj]
                        # end of loop

                # process any leftover history rules
                #base_qry = base_qry.filter (self.Q_from_Q_history  (allow_buf, deny_buf))
                base_Q = base_Q | self.Q_from_Q_history  (allow_buf, deny_buf)
                base_qry= base_qry.filter (base_Q)
                print base_qry
                print base_Q
                print_db_queries()
                return base_qry

        def Q_from_permission (self, rule):
                """ Returns a Q object according to the provided permission rule"""
                if rule.rows.startswith(RULE_CHAR):
                        # some escaping would prolly be needed here
                        empty,filter,filter_args = rule.rows.split(RULE_CHAR,2)
                        return ROW_FILTERS[filter] (filter_args)
                else:
                        pks = rule.rows.split(PK_SEP)
                        return Q(pk__in = pks)

        def Q_from_Q_history (self, allow_buf, deny_buf):
                positive_Q = Q()
                negative_Q = Q()
                for Q_obj in allow_buf:
                        positive_Q = positive_Q | Q_obj
                for Q_obj in deny_buf:
#			negative_Q = negative_Q & Q_obj
                        negative_Q = negative_Q | Q_obj
                return positive_Q & ~negative_Q

        def Q_from_samelevel_permissions (self, rules):
                """ Returns a Q object of all rules; rule_type == "deny" only if all rules are negative"""
                group_rule_type = DENY
                allow_buf = []
                deny_buf = []
                for rule in rules:
                        Q_obj = self.Q_from_permission (rule)
                        if rule.rule == ALLOW:
                                allow_buf.append (Q_obj)
                        elif rule.rule == DENY:
                                deny_buf.append (Q_obj)
                        else:
                                raise ValueError ('View permissions rules must match "%s" or "%s" - %s not understood' % (ALLOW, DENY,rule_type))
                if allow_buf:
                        group_rule_type = ALLOW
                return self.Q_from_Q_history(allow_buf,deny_buf), group_rule_type


        def hello (self):
                print self.model

class PermissionedModel(models.Model):
        """This is the class to be inherited by model classes """
        __metaclass__ = DUMPSModelBase

        class Meta:
                abstract = True
        objects = ModelManager()

class PermissionManager(models.Manager):
        """This is the manager of the Permission class"""
        def get_permission_rules (self,object_id, action, user):
                if not user:
                        return self.filter(object_id = object_id, action = action, actor='')
                groups = user.groups.values_list('name',flat=True)
                qry = self.filter(object_id = object_id, action = action).order_by('precedence')
                qry = qry.filter( (Q(actor_type='user') & Q(actor=user.username)) | (Q(actor_type='group') & Q(actor__in=groups) ) )
#		print qry
                return qry

        def get_grouped_permission_rules (self, *args, **kwargs):
                permissions = self.get_permission_rules (*args, **kwargs)
                grouped = {}
                for rule in permissions:
                        grouped.setdefault (rule.precedence, []).append (rule)
                return grouped

class Permission (models.Model):
        """This is the model holding the permission rules"""
# definition of each permission rule
        precedence = models.IntegerField (default = DEFAULT_PRECEDENCE)
        actor = models.CharField (max_length=MAX_FIELD_LEN,blank=True)
        actor_type = models.CharField (max_length=MAX_FIELD_LEN, choices = ACTOR_TYPES_CHOICES)
        object_id = models.CharField (max_length=MAX_FIELD_LEN)
        action = models.CharField (max_length=MAX_FIELD_LEN)
        rule = models.CharField (max_length=MAX_FIELD_LEN,  choices = RULES_CHOICES)
        columns = models.CharField (max_length=MAX_FIELD_LEN)
        rows = models.CharField (max_length=MAX_FIELD_LEN)

        objects = PermissionManager()

# META data
        class Admin:
                pass
# functions
        def __unicode__ (self):
                return u"%d - %s %s on '%s' for %s '%s' ; R:(%s), C:(%s)" % (self.precedence, self.rule, self.action, self.object_id, \
                                                                             self.actor_type, self.actor, self.rows,self.columns)
